package com.workingpub.commons.jdbc;

import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.apache.commons.lang3.CharEncoding;
import org.xml.sax.SAXException;

import com.workingpub.commons.jdbc.binding.ObjectFactory;
import com.workingpub.commons.jdbc.binding.SelectEntry;
import com.workingpub.commons.jdbc.binding.SqlDefineEntry;
import com.workingpub.commons.jdbc.exception.JdbcAccessException;

import freemarker.template.Configuration;
import freemarker.template.Template;

public class JaxbResolver {

	private JAXBContext jaxbContext;
	private Unmarshaller unmarshaller;
	private Marshaller marshaller;
	private Template querySqlFormater;

	private static JaxbResolver instance;

	public static final String SCHEMA_LANGUAGE = XMLConstants.W3C_XML_SCHEMA_NS_URI;
	public static final String SCHEMA_FILENAME = "com/workingpub/commons/jdbc/dapp-sqlDefines-1.0.xsd";
	/**
	 * JAXB mapping context path
	 */
	public static final String MAPPING_CONTEXT_PATH = "com.workingpub.commons.jdbc.binding";

	private JaxbResolver() {
		try {
			jaxbContext = JAXBContext.newInstance(MAPPING_CONTEXT_PATH, this.getClass().getClassLoader());
			unmarshaller = jaxbContext.createUnmarshaller();
			unmarshaller.setSchema(getSqlDefineSchema());

			marshaller = jaxbContext.createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
			marshaller.setProperty(Marshaller.JAXB_ENCODING, CharEncoding.UTF_8);

			Configuration cfg = new Configuration(Configuration.getVersion());
			cfg.setObjectWrapper(Configuration.getDefaultObjectWrapper(Configuration.getVersion()));
			cfg.setDefaultEncoding(CharEncoding.UTF_8);
			cfg.setClassLoaderForTemplateLoading(this.getClass().getClassLoader(),
					"com/workingpub/commons/jdbc/template");
			querySqlFormater = cfg.getTemplate("querySqlXml.ftl");
		} catch (Exception ex) {
			throw new JdbcAccessException(ex.getMessage(), ex);
		}
	}

	private Schema getSqlDefineSchema() {
		URL schemaUrl = null;
		schemaUrl = this.getClass().getClassLoader().getResource(SCHEMA_FILENAME);
		if (null == schemaUrl) {
			throw new JdbcAccessException(String.format("Did't fount the schema resource file : %s", SCHEMA_FILENAME));
		}
		SchemaFactory schemaFactory = SchemaFactory.newInstance(SCHEMA_LANGUAGE);
		try {
			return schemaFactory.newSchema(schemaUrl);
		} catch (SAXException e) {
			throw new JdbcAccessException(e.getMessage(), e);
		}
	}

	public static JaxbResolver getInstance() {
		return null == instance ? new JaxbResolver() : instance;
	}

	public SqlDefineEntry unmarshal(Source source) {
		try {
			JAXBElement<SqlDefineEntry> result = unmarshaller.unmarshal(source, SqlDefineEntry.class);
			return result.getValue();
		} catch (JAXBException e) {
			throw new JdbcAccessException(e.getMessage(), e);
		}
	}

	public SelectEntry unmarshalQuerySql(String sqlId, String sqlText) {
		try {
			StringWriter writer = new StringWriter();
			querySqlFormater.process(SqlParameters.create("sqlId", sqlId).put("sqlText", sqlText).map(), writer);
			JAXBElement<SelectEntry> jaxbElement = unmarshaller
					.unmarshal(new StreamSource(new StringReader(writer.toString())), SelectEntry.class);
			SelectEntry entry = jaxbElement.getValue();
			entry.setId(sqlId);
			return entry;
		} catch (Exception e) {
			throw new JdbcAccessException(e.getMessage(), e);
		}
	}

	public String marshal(SqlDefineEntry defineEntry) {
		StringWriter writer = new StringWriter();
		try {
			marshaller.marshal(new ObjectFactory().createSqlDefines(defineEntry), writer);
			return writer.toString();
		} catch (JAXBException e) {
			throw new JdbcAccessException(e.getMessage(), e);
		}
	}

	public String marshalSelectEntry(SelectEntry sqlMapping) {
		StringWriter writer = new StringWriter();
		try {
			marshaller.marshal(new ObjectFactory().createSelect(sqlMapping), writer);
			return writer.toString();
		} catch (JAXBException e) {
			throw new JdbcAccessException(e.getMessage(), e);
		}
	}
}
